/*
* Copyright (c) 2007 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/

#ifndef M_WINTHREAD_H
#define M_WINTHREAD_H

#ifdef WIN32
#include <windows.h>
#else
#include <pthread.h>
#endif

#include <stdexcept>

namespace Engine
{
	enum THREAD_PRIORITY
	{
		PRIORITY_LOW,
		PRIORITY_MEDIUM,
		PRIORITY_HIGH
	};

#ifdef WIN32
	/**
	*   Windows thread specific implementation.
	*/
	class ThreadImpl
	{
	public:	
		typedef unsigned long (*ThreadProc)(void*);

		struct ThreadStruct
		{
			ThreadProc function;
			void* param;
		};

		ThreadImpl():m_exitCode(0),m_hThread(0){}

		~ThreadImpl()
		{			
			CloseHandle(m_hThread);
		}

		void Create(unsigned long stackSize,ThreadProc func,void* param)
		{
			DWORD id;			
			m_st.function = func;
			m_st.param = param;
			m_hThread = ::CreateThread(NULL,stackSize,ThreadFunc,this,0,&id);
			if(m_hThread == NULL)
				throw std::logic_error("Can't create win32 thread.");
		}

		void WaitUntilDone()
		{
			if(IsCreated())
				WaitForSingleObject(m_hThread,INFINITE);
		}

		bool IsCreated()
		{
			return m_hThread != 0;
		}
		
		unsigned long GetExitCode()
		{
			return m_exitCode;
		}

		void SetPriority(int priority)
		{
			if(IsCreated())
				::SetThreadPriority(m_hThread,priority);
		}

		void Close()
		{
			// Closing a HANDLE twice is an error.
			// Allowing multiple calls to Initialize
			// requires this.
			if (IsCreated())
			{
				CloseHandle( m_hThread );
				m_hThread = 0;
			}
		}

	private:
		
		static DWORD CALLBACK ThreadFunc( LPVOID lpParam )
		{
			ThreadImpl* pTh = reinterpret_cast<ThreadImpl*>(lpParam);
			pTh->m_exitCode = pTh->m_st.function(pTh->m_st.param);
			return  pTh->m_exitCode;
		}

	private:
		unsigned long m_exitCode;
		HANDLE        m_hThread;
		ThreadStruct  m_st;
	};

#else

	class ThreadImpl
	{
	public:
		typedef unsigned long (*ThreadProc)(void*);

		struct ThreadStruct
		{
			ThreadProc function;
			void* param;
		};

		ThreadImpl():m_exitCode(0){}

		~ThreadImpl()
		{						
		}

		void Create(unsigned long stackSize,ThreadProc func,void* param)
		{
			pthread_attr_t attr;
			pthread_attr_init(&attr);
			pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);	
			pthread_attr_setstacksize(&attr,stackSize);
  
			m_st.function = func;
			m_st.param = param;
			m_created = pthread_create(&m_hThread, &attr, ThreadFunc, this);			
			pthread_attr_destroy(&attr);
			if(m_created)
				throw std::logic_error("Can't create pthread.");
		}

		void WaitUntilDone()
		{
			if(IsCreated())
				pthread_join(m_hThread, NULL);				
		}

		bool IsCreated()
		{
			return m_created == 0;
		}
		
		unsigned long GetExitCode()
		{
			return m_exitCode;
		}

		void SetPriority(int priority)
		{
			if(IsCreated())
			{
				sched_param param;
				switch(priority)
				{
				case PRIORITY_LOW:
					param.sched_priority = 0;
					break;
				case PRIORITY_MEDIUM:
					param.sched_priority = 5;
					break;
				case PRIORITY_HIGH:
					param.sched_priority = 10;
					break;
				}
				pthread_setschedparam(m_hThread, SCHED_OTHER, &param);
			}				
		}

		void Close()
		{
		}

	private:
		
		static void* ThreadFunc( void* lpParam )
		{
			ThreadImpl* pTh = reinterpret_cast<ThreadImpl*>(lpParam);
			pTh->m_exitCode = pTh->m_st.function(pTh->m_st.param);
			return NULL;
		}

	private:
		unsigned long m_exitCode;
		pthread_t     m_hThread;
		ThreadStruct  m_st;
		int           m_created;
	};
#endif

}

#endif
